package biz

import (
	"context"
	"errors"
	"fmt"
	cfg "github.com/go-kratos/kratos/contrib/config/etcd/v2"
	kratosConfigV2 "github.com/go-kratos/kratos/v2/config"
	"github.com/go-kratos/kratos/v2/log"
	jsoniter "github.com/json-iterator/go"
	clientv3 "go.etcd.io/etcd/client/v3"
	"sync/atomic"
)

var (
	// 全局的存储lottery信息的变量 Notice 也可以用 sync.Map
	globalLotteryKratos atomic.Value
)

type GlobalLotteryInterfaceKratos interface {
	// 获取所有活动的lottery数据
	GetAllActivitiesLotteriesKratos(ctx context.Context, fn func(context.Context) ([]Lottery, error)) (map[uint32]Lottery, error)
	// watch
	WatchUpdateLottery(ctx context.Context) error
}

type EtcdWatchCustomKratos struct {
	logger *log.Helper
	source kratosConfigV2.Source
}

func NewEtcdWatchCustomKratos(client *clientv3.Client, logger log.Logger) (GlobalLotteryInterfaceKratos, func()) {

	// 使用带cancel的ctx～
	ctx, cancel := context.WithCancel(context.Background())
	helper := log.NewHelper(logger, log.WithMessageKey("NewEtcdWatchCustomKratos"))

	// Notice 监听前缀 在cfg.New中加上 cfg.WithPrefix(true) ！！！！！
	source, errSource := cfg.New(client, cfg.WithPath(LotteryKeyPrefix), cfg.WithContext(ctx))
	if errSource != nil {
		fmt.Println("errSource: ", errSource)
		panic(errSource)
	}

	// 项目初始化的时候etcd中可能有数据，Load一下
	kvs, errKvs := source.Load()
	if errKvs != nil {
		helper.Errorf("初始化的时候Load报错! err: %v", errKvs)
	}
	if kvs != nil && len(kvs) > 0 {
		// Notice 只保存一个键值对，取第一个即可
		data := kvs[0]
		helper.Infof("Load lotteries: %v", string(data.Value))
		lotteries := make([]Lottery, 0)
		retMap := make(map[uint32]Lottery)
		// data.Value 是 byte 类型的
		err := jsoniter.Unmarshal(data.Value, &lotteries)
		if err != nil {
			helper.Errorf("json反序列化data.Value失败了! err: %v", err)
		}
		if len(lotteries) > 0 {
			for _, item := range lotteries {
				retMap[item.ActivityId] = item
			}
			globalLotteryKratos.Swap(retMap)
		}
	}

	g := &EtcdWatchCustomKratos{
		source: source,
		logger: helper,
	}

	// Notice 启动的时候 开一个协程去 watch
	// Notice ———— 用的时候记得打开！！！！！
	go func() {
		err := g.WatchUpdateLottery(ctx)
		if err != nil {
			helper.Errorf("watchUpdateLottery出错了: %v ", err)
		}
	}()

	return g, func() {
		cancel()
	}
}

func (e *EtcdWatchCustomKratos) WatchUpdateLottery(ctx context.Context) error {
	e.logger.Infof("begin watching key %v", LotteryKeyPrefix)

	watcher, errWatcher := e.source.Watch()
	if errWatcher != nil {
		return errors.New(fmt.Sprintf("watchUpdateLottery时Watch出错: %v", errWatcher))
	}

	// 死循环监听
	for {
		select {
		// context Done 了，回收资源
		case <-ctx.Done():
			errStop := watcher.Stop()
			if errStop != nil {
				return errStop
			}
			return nil
		default:
			kvs, errKvs := watcher.Next()
			if errKvs != nil {
				return errors.New(fmt.Sprintf("errKvs: %v", errKvs))
			}
			// Notice 只保存一个键值对，取第一个即可
			if kvs != nil && len(kvs) > 0 {
				data := kvs[0]
				e.logger.Infof("watch lotteries changes: %v", string(data.Value))
				lotteries := make([]Lottery, 0)
				retMap := make(map[uint32]Lottery)
				// data.Value 是 byte 类型的
				err := jsoniter.Unmarshal(data.Value, &lotteries)
				if err != nil {
					e.logger.Errorf("json反序列化data.Value失败了! err: %v", err)
				}
				if len(lotteries) > 0 {
					for _, item := range lotteries {
						retMap[item.ActivityId] = item
					}
					globalLotteryKratos.Swap(retMap)
				}
			}
		}
	}
}

// Notice 实际中获取本地内存中lotter数据的方法
func (e *EtcdWatchCustomKratos) GetAllActivitiesLotteriesKratos(ctx context.Context, fn func(context.Context) ([]Lottery, error)) (map[uint32]Lottery, error) {
	// Load
	val := globalLotteryKratos.Load()
	fmt.Println("val:>>> ", val)

	// 本地缓存中没有数据
	if val == nil {
		if fn == nil {
			return nil, errors.New("mission func to get lotteries")
		}
		// Notice 调用传来的函数 从 DB或Redis 中获取lotteries
		lotteries, err := fn(ctx)
		retMap := make(map[uint32]Lottery, 0)
		if err != nil {
			return nil, err
		}
		if len(lotteries) > 0 {
			//groupLotteriesMap := slice.GroupWith(lotteries, func(item Lottery) uint32 {
			//	return item.ActivityId
			//})
			for _, item := range lotteries {
				retMap[item.ActivityId] = item
			}
			// Store
			globalLotteryKratos.Store(retMap)
			return retMap, nil
		}
	}

	// 本地缓存中有数据
	lotteriesMap, ok := val.(map[uint32]Lottery)
	if !ok {
		return nil, errors.New("no lotteries cache!")
	}

	return lotteriesMap, nil
}
